!lm10
!rm76
Using Applesoft ROM's from Assembly Language

There are many useful entry points in the Applesoft ROM's.  The problem is figuring out how to use them.  John Crossley's article "Applesoft Internal Entry Points" (originally published in Apple Orchard Volume 1 Number 1 March 1980) gives a brief description of most of the usable subroutines.  If you missed the article, you can still get it from the International Apple Corps.  It has also recently been reprinted in "Call A.P.P.L.E. in Depth--All About Applesoft".

Now I want to show you how to use the floating point math subroutines.  I won't cover every one of them, but enough to do most of the things you would ever need to do.  This includes load, store, add, subtract, complement, compare, multiply, divide, print, and formatted-print.


Internal Floating Point Number Format

Applesoft stores floating point numbers in five bytes.  The first byte is the binary exponent; the other four bytes are the mantissa:  ee mm mm mm mm.

The exponent (ee) is a signed number in excess-$80 form.  That is, $80 is added to the signed value.  An exponent of +3 will be stored as $83; of -3, as $7D.  If ee = $00, the entire number is considered to be zero, regardless of what the mantissa bytes are.

The mantissa is considered to be a fraction between $.80000000 and $.FFFFFFFF.  Since the value is always normalized, the first bit of the mantissa is always "1".  Therefore, there is no need to actually use that bit position for a mantissa bit.  Instead, the sign of the number is stored in that position (0 for +, 1 for -).  Here are some examples:

!lm15
-10.0   84 A0 00 00 00
+10.0   84 20 00 00 00
+1.0    81 00 00 00 00
+1.75   81 60 00 00 00
-1.75   81 E0 00 00 00
+.1     7D 4C CC CC CD

!lm10
The Applesoft math subroutines use a slightly different format for faster processing, called "unpacked format".  In this format the leading mantissa bit is explicitly stored, and the sign value is stored separately.  Several groups of page-zero locations are used to store operands and results.  The most frequently used are called "FAC" and "ARG".  FAC occupies locations $9D thru $A2; ARG, $A5 thru $AA.

Loading and Storing Floating Point Values

There are a handful of subroutines in ROM for moving numbers into and out of FAC and ARG.  Here are the five you need to know about.

!lm15
AS.MOVFM   $EAF9   unpack (Y,A) into FAC
AS.MOVMF   $EB2B   pack FAC into (Y,X)
AS.MOVFA   $EB53   copy ARG into FAC
AS.MOVAF   $EB63   copy FAC into ARG
AS.CONUPK  $E9E3   unpack (Y,A) into ARG
!lm10

All of the above subroutines return with the exponent from FAC in the A-register, and with the Z-status bit set if (A)<0.

Here is an example which loads a value into FAC, and then stores it at a different location.
!lm15

LDA #VAR1
LDY /VAR1    ADDRESS IN (Y,A)
JSR AS.MOVFM
LDX #VAR2
LDY /VAR2    ADDRESS IN (Y,X)
JSR AS.MOVMF

!lm10
Arithmetic Subroutines

Once a number is unpacked in FAC, there are many subroutines which can operate on it.

!lm15
AS.NEGOP   $EED0   FAC = -FAC

AS.FOUT    $ED34   convert FAC to decimal ASCII string
                   starting at $0100

AS.FCOMP   $EBB2   compare FAC to packed number at (Y,A)
                   return (A) = 1 if (Y,A) < FAC
                          (A) = 0 if (Y,A) = FAC
                          (A) =FF if (Y,A) > FAC

AS.FADD    $E7BE   load (Y,A) into ARG, and fall into...
AS.FADDT   $E7C1   FAC = ARG + FAC

AS.FSUB    $E7A7   load (Y,A) into ARG, and fall into...
AS.FSUBT   $E7AA   FAC = ARG - FAC

AS.FMUL    $E97F   load (Y,A) into ARG, and fall into...
AS.FMULT   $E982   FAC = ARG * FAC

AR.FDIV    $EA66   load (Y,A) into ARG, and fall into...
AS.FDIVT   $EA69   FAC = ARG / FAC

!lm10
Here is an example which calculates VAR1 = (VAR2 + VAR3) / (VAR2 - VAR3).
!lm15
LDA #VAR2   VAR2+VAR3
LDY /VAR2
JSR AS.MOVFM    VAR2 INTO FAC
LDA #VAR3
LDY /VAR3
JSR AS.FADD     + VAR3
LDX #VAR1
LDY /VAR1
JSR AS.MOVMF    STORE SUM TEMPORARILY IN VAR1
LDA #VAR3   VAR2-VAR3
LDY /VAR3
JSR AS.MOVFM    VAR3 INTO FAC
LDA #VAR2
LDY /VAR2
JSR AS.FSUB     VAR2-VAR3
LDA #VAR1
LDY /VAR1
JSR AS.FDIV     DIVIDE DIFFERENCE BY SUM
LDX #VAR1
LDY /VAR1
JSR AS.MOVMF    STORE THE QUOTIENT

!lm10
As you can see, it is easy to get confused when writing this kind of code.  It is so repetitive, there are so many setups of (Y,A) and (Y,X) addresses, that I make a lot of typing mistakes.  It would be nice if there was an interface program between my assembly language coding and the Applesoft ROMs.  I would rather write the above program like this:

!lm15
JSR FP.LOAD   VAR2 INTO FAC
.DA VAR2
JSR FP.SUB    -VAR3
.DA VAR3
JSR FP.STORE  SAVE AT VAR1
.DA VAR0
JSR FP.LOAD   VAR2 INTO FAC
.DA VAR2
JSR FP.ADD    +VAR3
.DA VAR3
JSR FP.DIV    /(VAR2-VAR3)
.DA VAR1
JSR FP.STORE  STORE IN VAR1
.DA VAR1

!lm10

Easy Interface to Applesoft ROMs

The first step in constructing the "easy interface" is to figure out a way to get the argument address from the calling sequence.  That is, when I execute:
     JSR FP.LOAD
     .DA VAR1
how does FP.LOAD get the address VAR1?

I wrote a subroutine called GET.ADDR which does the job.  Every one of my FP. subroutines starts by calling GET.ADDR to save the A-, X-, and Y-registers, and to return with the address which followed the JSR FP... in the Y- and A-registers.  In fact, I return the low-byte of the address in both the A- and X-registers.  That way the address is ready in both (Y,A) and (Y,X) form.

GET.ADDR is at lines 4260-4480.  I save A, X, and Y in three local variables, and then pull off the return address from the stack and save it also.  (This is the return to whoever called GET.ADDR).  Then I save the current TXTPTR value.  This is the pointer Applesoft uses when picking up bytes from your program to interpret them.  I am going to borrow the CHRGET subroutine, so I need to save the current TXTPTR and restore it when I am finished.  Then I pull the next address off the stack and stuff it into TXTPTR.  This address is the return address to whoever called the FP... subroutine.  It currently points to the third byte of that JSR, one byte before the .DA address we want to pick up.

I next call GET.ADDR2, which uses CHRGET twice to pick up the next two bytes after the JSR and returns them in X and Y.  Then I push the return address I saved at the beginning of GET.ADDR, and RTS back.  Note that TXTPTR now points at the second byte of the .DA address.  It is just right for picking up another argument, or for returning.  If there is another argument, I get it by calling GET.ADDR2 again.  When I am ready for the final return, I do it by JMPing to FP.EXIT.

FP.EXIT, at lines 4670-4790, pushes the value in TXTPTR on the stack.  It is the correct return address for the JSR FP....  Then I restore the old value of TXTPTR, along with the A-, X-, and Y-registers.  And the RTS finishes the job.


The Interface Subroutines

I have alluded above to the "FP..." subroutines.  In the listing I have shown eight of them, and you might add a dozen more after you get the hang of it.

!lm15
FP.LOAD        load a value into FAC
FP.STORE       store FAC at address
FP.ADD         FAC = FAC + value
FP.SUB         FAC = FAC - value
FP.MUL         FAC = FAC * value
FP.DIV         FAC = FAC / value
FP.PRINT       print value the way Applesoft would
FP.PRINT.WD    print value with D digits after decimal
               in a W-character field

!lm10
FP.LOAD, FP.STORE, FP.ADD, and FP.MUL are quite straightforward.  All they do is call GET.ADDR to get the argument address, JSR into the Applesoft ROM subroutine, and JMP to FP.EXIT.

FP.SUB and FP.DIV are a little more interesting.  I didn't like the way the Applesoft ROM subroutines ordered the operands.  It looks to me like they want me to think in complements and reciprocals.  Remember that AS.FDIV performs FAC = (Y,A) / FAC.  It is more natural for me to think left-to-right, so my FP.DIV permorms FAC = FAC / value.  Likewise for FP.SUB.

I reversed the sense of the subtraction after-the-fact, by just calling AS.NEGOP to complement the value in FAC.  Reversing the division has to be done before calling AS.FDIV.  I saved the argument address on the stack, called AS.MOVAF to copy FAC into ARG, called AS.MOVFM to get the argument into FAC, and then called AS.FDIVT.

FP.PRINT, at lines 1830-1930, is also quite simple.  I call GET.ADDR to set up the argument address, and AS.MOVFM to load it into FAC.  Then AS.FOUT converts it to an ASCII string starting at $0100.  It terminates with a $00 byte.  A short loop picks up the characters of this string and prints them by calling AS.COUT.  I called AS.COUT, rather than $FDED in the monitor, so that Applesoft FLASH, INVERSE, and NORMAL would operate on the characters.

And now for the really interesting one....


Formatted Print Subroutine

FP.PRINT.WD expects three arguments:  the address of the value to be printed, the field width to print it in, and the number of digits to print after the decimal point.  Leading blanks and trailing zeroes will be printed if necessary.  The Applesoft E-format will be caught and converted to the more civilized form.  Fields up to 40 characters wide may be printed, which will accommodate up to 39 digits and a decimal point.  If you try to print a number that is too wide for the field, it will try to fit it in by shifting off fractional digits.  If it is still too wide, it will print a field of ">>>>" indicating overflow.

For example, look at how values 123.4567and 12345.67 would be printed for corresponding W and D:

!lm15
 W    D     123.4567    12345.67
---------------------------------
10    1    bbbbb123.4  bbb12345.6
10    3    bbb123.456  b12345.670
10    5    b123.45670  12345.6700
10    7    123.456700  12345.6700
 7    1       bb123.4     12345.6
 4    1          123.        >>>>

!lm10
Sound pretty useful?  I can hardly wait to start using it!  Now let's walk through the code.

Lines 2380-2410 pick up the arguments.  The value is loaded into FAC, and converted to a string at $0100 by AS.FOUT.  Then I get the W and D values into X and Y.

Lines 2420-2510 check W and D.  W must not be more than 40; if it is, use 40.  (I arbitrarily chose 40 as the limit.  If you want a different limit, you can use any value less than 128.)   I also make sure that D is less than W.  I save W in WD.GT in case I later need to print a field full of ">".  Lines 2520-2560 compute W-D-1, which is the number of characters in the field to the left of the decimal point.  I save the result back in W.

Lines 2570-2590 check whether AS.FOUT converted to the Applesoft E-format or not.  The decimal exponent printed after E is still in $9A as a binary value.  Numbers formatted the civilized way are handled by lines 2600-3160.  E-format numbers are restructured by lines 3200-3930.

Lines 2600-2750 scan the string at $0100 up to the decimal point (or to the end if no decimal point).  In other words, I am counting the number of characters AS.FOUT put before the decimal point.  If W is bigger than that, the difference is the number of leading blanks I need to print.  Since W is decremented inside the loop, the leading blank count is all that is left in W.  But what if W goes negative, meaning that the number is too big for the field?  Then I reduce D and try again.  If I run out of "D" also, then the field is entirely too small, so I go to PRINT.GT to indicate overflow.  If there was no decimal point on the end, the code at lines 2790-2820 appends one to the string.

Lines 2870-2980 scan over the fractional digits.  If there are more than D of them, I store the end-of-string code ($00) after D digits.  I also decrement D inside this loop, so that when the loop is finished D represents the number of trailing zeroes that I must add to fill out the field.  (If the string runs out before D does, I need to print trailing zeroes.)

At line 3020, the leading blanks are printed (if any; remember that W had the leading blank count).  Then lines 3060-3110 print the string at $0100.  And finally, line 3150 prints out D trailing zeroes (D might be zero).

E-formatted numbers are a little tougher; we have to move the decimal point left or right depending on the exponent.  We also might have to add zeroes before the decimal point, as well as after the fraction.  Lines 3200-3330 scan through the converted string at $0100; the decimal point (if any) is removed, and an end-of-string byte ($00) is put where the "E" character is.  Now all we have at $0100 is the sign and a string of significant digits, without decimal point or E-field.

Lines 3350-3600 test the range of the decimal exponent.  Negative exponents are handled at lines 3370-3660, and positive ones at lines 3700-3930.

Negative exponents mean that the decimal point must be printed first, then possibly some leading zeroes, and then some significant digits.  Lines 3370-3410 compute how many leading zeroes are needed.  For example, the value .00123 would be converted by AS.COUT as "1.23E-03".  The decimal exponent is -3, and we need two leading zeroes.  The number of leading zeroes is -(dec.exp+1).

There is a little coding trick at line 3370.  I want to compute -(dec.exp+1), and dec.exp is negative.  By executing the EOR #$FF, the value is complemented and one is added at the same time!  Why?  Because the 6502 uses 2's complement arithmetic.  Negative numbers are in the form 256-value.  EOR #$FF is the same as doing 255-value, which is the same as 256-(value+1).  Got it?

Line 3430 prints the leading blanks; lines 3450-3460 print the decimal point.  Lines 3480-3520 print the leading zeroes, decrementing D along the way.  When all the leading zeroes are out, D will indicate how many significant digits need to be printed.

Lines 3540-3620 print as many significant digits as will fit in the remaining part of the field (maybe none).  Of course, the field might be large enough that we also need trailing zeroes.  If so, line 3650 prints them.

What if the exponent was positive?  Then lines 3700-3710 see if the number will fit in the field.  If not, PRINT.GT will fill the field with ">".  If it will fit, then the exponent is the number of digits to be printed.  The number of leading blanks will be W-dec.exp-1 (the -1 is for the decimal point).  Note that line 3740 complements and adds one at the same time, to get -(exp+1).

Line 3770 prints the leading blanks, if any.  Lines 3780-3830 print the significant digits from the string at $0100.  Lines 3840-3890 print any zeroes needed between the significant digits and the decimal point.  Lines 3900-3910 print the decimal point, and line 3920 prints the trailing zeroes.


Possible Modifications

You might like to add a dozen or so more FP... subroutines, and hand-compile your favorite Applesoft programs into machine language.  You might want to revise the FP.PRINT.WD subroutine to work from Applesoft using the & statement, or using a CALL.  This would give you a very effective way of formatting values.  You also might want to make it put the result in an Applesoft string variable, rather than directly printing it.  You might want to add a floating dollar sign capability, or comma insertion between every three digits.  If you implement any of these, let me know.  I would like to print them in future issues of AAL.
